#include "timer.h"

#include <sys/timerfd.h>
#include <unistd.h>

#include "common/exception.h"
#include "common/log.h"


namespace evm { namespace event {

PeriodTimer::PeriodTimer(double initial, double period, TimerHandler handler, EventLoop* loop)
        : loop_(loop) {

    /* Create monotonic, non-blocking, close-on-exec timerfd. */
    fd_ = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
    check_unix_error(fd_, "timerfd_create");

    set(initial, period, handler);

    loop_->add_event(fd_, EV_READABLE, [this](int, int) {
        uint64_t exp;
        if (read(fd_, &exp, sizeof(exp)) != sizeof(exp))
            throw_system_error("read");

        if (handler_)
            handler_();
    });
}

PeriodTimer::~PeriodTimer() {
    loop_->rmv_event(fd_, EV_READABLE);
    close(fd_);
}

void PeriodTimer::set(double initial, double period, TimerHandler handler) {
    if (handler)
        handler_ = handler;

    itimerspec its;
    its.it_value.tv_sec = static_cast<time_t>(initial);
    its.it_value.tv_nsec = static_cast<long>((initial - its.it_value.tv_sec) * 1e9);
    its.it_interval.tv_sec = static_cast<time_t>(period);
    its.it_interval.tv_nsec = static_cast<long>((period - its.it_interval.tv_sec) * 1e9);
    check_unix_error(timerfd_settime(fd_, 0, &its, nullptr), "timerfd_settime");
}

void PeriodTimer::stop() {
    set(0, 0);
}

OneshotTimer::OneshotTimer(double initial, TimerHandler handler, EventLoop* loop)
        : PeriodTimer(initial, 0, handler, loop) {
}

void OneshotTimer::set(double initial, TimerHandler handler) {
    PeriodTimer::set(initial, 0, handler);
}

}}
